package main

import (
	"context"
	"fmt"
	"time"
)

func main() {
	/*
		context包:
		    Go语言中 的 context.Context 的主要作用还是在多个 Goroutine 组成的树中同步取消信号以减少对资源的消耗和占用，
		虽然它也有传值的功能，但是这个功能我们还是很少用到。

		1.Context接口：
		（1）type Context interface {
				//返回 context.Context 被取消的时间，也就是完成工作的截止日期
				Deadline() (deadline time.Time, ok bool)

				//返回一个 channel，这个 channel 会在 当前工作完成 或者 上下文取消 之后关闭，多次调用 Done 方法会返回 “同一个 channel”
				Done() <-chan struct{}

				//返回 context.Context 结束的原因，它只会在 Done 返回的 channel 被关闭时才会返回空值；
				//如果 context.Context 被取消，会返回 channel 错误；
				//如果 context.Context 超时，会返回 DeadlineExceeded 错误
				Err() error

				//从 context.Context 中获取键对应的值，对于同一个上下文来说，多次调用 Value 并传入相同的 key 会返回相同的结果
				//该方法可以用来传递请求特定的数据
				Value(key interface{}) interface{}
			}

		2.创建 Context
			（1）context.Background():
					这个函数返回一个空 Context。这只能用于高等级（在 main 或顶级请求处理中）
			（2）context.TODO():
					这个函数也是创建一个 Context。也只能用于高等级 或 当您不确定使用什么 Context，或 函数以后会更新以便接收一个 Context。
					这意味着您（或维护者）计划将来要添加 Context 到函数

			其实，源代码中，它们两个都是通过 new(emptyCtx) 语句初始化的，它们是指向私有结构体 context.emptyCtx 的指针。

			从源码来看，context.Background 和 context.ToDO 函数其实也只是互为别名，没有太大的差别。
			只是在使用 和 语义上稍有不同：
			（1）context.Background 是上下文默认值，所有其他的上下文都应该从它 衍生出来
			（2）context.TODO 应该只在 不确定应该使用哪种上下文 时使用；
				在大多数情况下，如果当前函数没有上下文作为入参，我们都会使用 context.Background 作为其实的上下文传递。

		3.Context 的继承衍生：

			（1）func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
				1、用来取消 Context 中的操作。可以方便地实现对 一组 相关操作 的取消功能。
				2、参数：一个 父Context。
				  返回值：一个 子Context，以及一个 取消函数（cancel） 用来取消 Context。
					当调用 cancel 函数时，Context 中的所有操作都会被取消。

				3、在创建 子goroutine 时，也可以将 父Context 传递给 子goroutine，并在需要 取消 子goroutine 时，通过调用 cancel 函数来实现。
					这样可以避免因为 子goroutine 的长时间运行而导致的资源泄露。

				4、例如：在一个 HTTP 请求处理器中，可能需要调用多个后端服务来处理请求，并等待它们的响应。
					使用 WithCancel 函数创建一个 Context 对象，可以将这些操作关联到同一个 Context 中，然后在需要取消所有操作时，只需调用 cancel 函数即可。

			（2）func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
				1、和 WithCancel 函数差不多，它会多传递一个 截止时间 参数，意味着 到了这个 deadline（时间点），会自动取消 Context，
				  当然也可以不等到 截止时间，可以提前通过 取消函数 进行取消。
					通过调用返回的 CancelFunc 函数 可以释放 与该上下文相关联的 资源和 goroutine。
				2、如果 parent 上下文对象 已经过期 或者 deadline 已经先于传入的 deadline 时间，则返回的上下文也会立即过期。
				3、注：
					虽然通过 WithDeadline 可以设置一个 截止时间，在上下文过期后可以通知所有与之相关联的 goroutine 停止操作，
				  但这并不代表操作一定能在 截止时间 之前完成。有些操作可能在处理大量数据 或者 进行复杂计算 时需要较长时间才能完成，
				  因此，在设计程序逻辑时，需要考虑到这种情况，并合理设置超时时间 或者 采用其他手段来确保程序的正确执行。

			（3）func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
				1、和 WithDeadline 函数基本上一样，这个函数表示的是 超时自动取消，是 timeout（时间段）后 自动取消 Context。
					timeout 是一个 time.Duration 类型的 时间间隔，表示多久之后自动取消上下文。
					返回的 新上下文 会继承 父上下文 的所有信息，并添加一个超时时间，当超过超时时间后，该上下文中的 任何操作 都将会被取消。
					返回的 CancelFunc 函数可以用来手动取消该上下文。

			（4）func WithValue(parent Context, key, val interface{}) Context
				1、创建一个包含指定 key-value 对的新上下文（Context）。
				2、参数：父上下文（parent）、键（key）、值（val）
				  返回值：一个新的 Context 对象。
				3、当我们需要在一个函数中传递多个相关联的值时，可以使用这个函数来创建一个新的上下文对象，
				并把所有的值都存储到这个对象当中。然后可以将这个上下文对象传递给调用的子函数，并在其中使用 Value 函数 获取相应的值。
				4、注：
				（1）使用 WithValue 函数创建的 新上下文对象 会继承 父上下文对象的 Deadline 和 Cancel 方法。
				当 父上下文对象 被取消 或 超时 的时候，其所有的 子上下文对象 也会随之取消。
				（2）由于 Context对象 是不可变的，因此不能通过对其 现有值 进行更改来修改 已经存储在 其中的值。
				如果要修改上下文，必须使用新上下文替换现有的上下文。

			总结：
				这 4 个 With 函数，接收的都有一个 parent 参数，就是 父Context，我们要基于这个 父Context 创建出子 Context 的意思，
				这种方式可以理解为 子Context 对 父Context 的继承，也可以理解为 基于 父Context 创建出来的 衍生。
				通过这些函数就创建了一颗Context树，树的每个节点都可以有任意多个子节点，节点层级可以有任意多个。

		4.Context 使用原则：
		（1）context.Background 只应用在最高等级，作为所有派生词 Context 的 根
		（2）Context 取消是建议性的，这些函数可能需要一些时间来清理和退出
		（3）不要把 Context 放在结构体中，要以参数的方式传递
		（4）以 Context 作为参数的函数方法，应该把 Context 作为第一个参数，放在第一位
		（5）给一个函数方法传递 Context 的时候，不要传递 nil，如果不知道传递什么，就使用 context.TODO
		（6）Context 的 Value 相关方法应该传递必须的数据，不要什么数据都使用这个传递
		（7）context.Value() 应该很少使用，它不应该被用来传递可选参数。这使得 API 隐式的 并且可以引起错误。
			取而代之的是，这些值应该作为参数传递。
		（8）Context 是线程安全的，可以放心的在多个 goroutine 中传递。
			同一个 Context 可以传给使用其的多个 goroutine，且 Context 可被多个 goroutine 同时安全访问
		（9）Context 结构没有取消方法，因为只有派生 Context 的函数才应该取消 Context

	*/
	fmt.Println("------------------WithCancel------------------")
	parent0 := context.Background()

	//使用 WithCancel 函数创建一个可以取消的子上下文
	ctx0, cancel0 := context.WithCancel(parent0)

	//启动一个 goroutine，在其中执行一些工作并定期检查 ctx0 是否已经被取消
	go func() {
		for true {
			select {
			case <-ctx0.Done():
				fmt.Println("子上下文已被取消")
				return
			default:
				fmt.Println("正在执行工作。。。")
				time.Sleep(1 * time.Second)
			}
		}
	}()

	//等待 5 秒钟后取消子上下文
	time.Sleep(5 * time.Second)
	cancel0()

	//等待 2 秒钟，以便能够看到输出信息
	time.Sleep(2 * time.Second)
	/*
		正在执行工作。。。
		正在执行工作。。。
		正在执行工作。。。
		正在执行工作。。。
		正在执行工作。。。
		子上下文已被取消
	*/

	fmt.Println("-----------------WithDeadline------------------")

	/*
		在这个示例中，我们使用了WithDeadline函数创建了一个带有截止时间的Context对象，并将其传递给了select语句的一个case分支。
		由于截止时间为2秒后，而select语句等待3秒钟才会触发，因此该Context对象会在2秒后自动取消，最终输出"context deadline exceeded"。
	*/
	parent := context.Background()
	ctx, cancel := context.WithDeadline(parent, time.Now().Add(2*time.Second))
	defer cancel()

	select {
	case <-time.After(3 * time.Second):
		fmt.Println("overslept")
	case <-ctx.Done():
		fmt.Println(ctx.Err(), "case 语句中的 <-ctx.Done() 的值为零值，因为读取关闭的通道会返回零值")
	}
	/*
		context deadline exceeded case 语句中的 <-ctx.Done() 的值为空，因为读取关闭的通道会返回零值
	*/

	fmt.Println("------------------------------WithTimeout--------------------------")

	/*
		在这个示例中，我们使用了WithTimeout函数创建了一个带有超时时间的Context对象。与WithDeadline不同，
		WithTimeout接受一个duration参数，表示超时时间。
		在本例中，超时时间为2秒钟。由于select语句等待3秒钟才会触发，因此该Context对象也会在2秒钟后自动取消，输出"context deadline exceeded"。
	*/
	parent1 := context.Background()
	ctx1, cancel1 := context.WithTimeout(parent1, 2*time.Second)
	defer cancel1()

	select {
	case <-time.After(3 * time.Second):
		fmt.Println("overslept")
	case <-ctx1.Done():
		fmt.Println(ctx.Err())
	}
	/*
		context deadline exceeded
	*/

	fmt.Println("------------------------WithValue---------------------")

	fmt.Println("---------实例 1---------")

	parent2 := context.Background()
	ctx2 := context.WithValue(parent2, "User", &User{Name: "喜羊羊", Age: 18})

	fmt.Println(ctx2.Value("User"))
	fmt.Println(ctx2.Value("User").(*User)) //将值作为 *User 输出（这是断言）
	fmt.Println(ctx2.Value("User").(*User).Name)
	fmt.Println(ctx2.Value("User").(*User).Age)
	/*
		&{喜羊羊 18}
		&{喜羊羊 18}
		喜羊羊
		18
	*/

	fmt.Println("----------实例 2----------")

	ctx3 := context.WithValue(context.Background(), "灰太狼说：", "你好，喜羊羊！")

	//从上下文中检索值
	data := ctx3.Value("灰太狼说：")

	//将值作为字符串输出
	fmt.Println(data.(string))
	/*
		你好，喜羊羊！
	*/

}

type User struct {
	Name string
	Age  int
}
